#region Licence
// Copyright (c) 2008 Tasman Logic Ltd

// Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
// and associated documentation files (the "Software"), to deal in the Software without restriction, 
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all copies 
// or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System.Collections.Generic;
using System.Reflection;

using ActiveSharp.PropertyMapping;

using NUnit.Framework;
using System;

namespace ActiveSharp.Tests
{
    /// <summary>
    /// A version of <see cref="PropertyMapTests"/> that tests the new "fast" API (<see cref="FastPropertyMap")/>
    /// </summary>
    [TestFixture]
    public class PropertyMapTests_FastPropertyMap
    {
        [Test]
        public void CanDetectPropertyChange()
        {
            TestClass t = new TestClass();
            t.Foo = "5";
            Assert.AreEqual("Foo", t.LastChangedPropertyName);
            t.Bar = 5;
            Assert.AreEqual("Bar", t.LastChangedPropertyName);
            Assert.AreEqual(0.0, t.Baz);
            Assert.AreEqual("Baz", t.LastChangedPropertyName);
        }

        /// <summary>
        /// You just can't use the fast one in properties of abstract classes. Workaround is to use
        /// <see cref="PropertyMap"/> there instead.
        /// </summary>
        [Test]
        public void CanNOTDetectPropertyChangeInAbstractBase_UsingFastPropertyMap()  
        {
            try
            {
                TestClass t = new TestClass();
                t.Foo2 = "5";
                Assert.Fail("Expected exception");
            }
            catch (Exception ex)
            {
                Assert.That(ex.ToString().Contains("Cannot map offsets for abstract class"));
            }
        }

        [Test]
        public void CanDetectPropertyChangeInDerivedClass()
        {
            DerivedTestClass t = new DerivedTestClass();
            t.Foo = "5";
            Assert.AreEqual("Foo", t.LastChangedPropertyName);
            t.Bar = 5;
            Assert.AreEqual("Bar", t.LastChangedPropertyName);
            t.DerivedProp1 = true;
            Assert.AreEqual("DerivedProp1", t.LastChangedPropertyName);
            Assert.AreEqual(0.0, t.Baz);
            Assert.AreEqual("Baz", t.LastChangedPropertyName);
        }

        abstract class AbstractTestClass
        {
            protected T CoreGetValue<T>(ref T field, string propertyName)
            {
                LastChangedPropertyName = propertyName;
                return field;
            }

            protected void CoreSetValue<T>(ref T field, T value, string propertyName)
            {
                LastChangedPropertyName = propertyName;
                field = value; // todo, add some code to see if the value has really changed or not...
            }

            private string _lastChangedPropertyName;

            public string LastChangedPropertyName
            {
                get { return _lastChangedPropertyName; }
                private set { _lastChangedPropertyName = value; }
            }

            private string _foo2;

            private void SetValue<T>(ref T field, T value)
            {
                // NOTE this won't actually work, because we are in an abstract class - we assert the failure in our tests
                CoreSetValue(ref field, value, FastPropertyMap<AbstractTestClass>.GetPropertyName(this, ref field));
            }

            public string Foo2
            {
                get { return _foo2; }
                set { SetValue(ref _foo2, value); }
            }
        }

        abstract class AbstractTestClass2 : AbstractTestClass
        {
            // nothing in this class, but retained to parallel the "non-fast" version of these tests (for the original PropertyMap)
        }

        class TestClass : AbstractTestClass2
        {
            private string _foo;
            private int _bar;
            private decimal _baz;

            private void SetValue<T>(ref T field, T value)
            {
                CoreSetValue(ref field, value, FastPropertyMap<TestClass>.GetPropertyName(this, ref field));
            }

            private T GetValue<T>(ref T field)
            {
                return CoreGetValue(ref field, FastPropertyMap<TestClass>.GetPropertyName(this, ref field));
            }

            public string Foo
            {
                get { return _foo; }
                set { SetValue(ref _foo, value); }
            }

            public int Bar
            {
                get { return _bar; }
                set { SetValue(ref _bar, value + 5); // just adding something to value to make the MSIL harder to parse in our test
                }
            }

            public decimal Baz
            {
                get
                {
                    return GetValue(ref _baz); 
                }
            }
        }

        class DerivedTestClass : TestClass
        {
            private bool _derivedProp1;

            private void SetValue<T>(ref T field, T value)
            {
                CoreSetValue(ref field, value, FastPropertyMap<DerivedTestClass>.GetPropertyName(this, ref field));
            }

            private T GetValue<T>(ref T field)
            {
                return CoreGetValue(ref field, FastPropertyMap<DerivedTestClass>.GetPropertyName(this, ref field));
            }

            public bool DerivedProp1
            {
                get { return _derivedProp1; }
                set { SetValue(ref _derivedProp1, value); }
            }
        }
    }
}